package com.google.code.pokerstats_service;

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.google.code.pokerstats_service.math.BasicStatsCalculator;
import com.google.code.pokerstats_service.math.GroupExpectationCalculator;
import com.google.code.pokerstats_service.math.PokerRankCalculator;
import com.google.code.pokerstats_service.math.RiskOfRuinCalculator;
import com.google.code.pokerstats_service.math.RivalsCalculator;

public class PokerStatsService {
    
    public PokerStatsService() {
        try {
            CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
            memCache = cacheFactory.createCache(Collections.emptyMap());
        } catch (CacheException e) {}
    }
    
    @SuppressWarnings("unchecked")
    public Set<Game> getGames(Long groupId) {
        Set<Game> games = new HashSet<Game>();
        
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            Query gamesQuery = pm.newQuery(Game.class);
            gamesQuery.setFilter("groupId == groupIdParam");
            gamesQuery.declareParameters("Long groupIdParam");
            
            List<Game> gamesList = (List<Game>) gamesQuery.execute(groupId);
            for (Game game : gamesList) {
                games.add(game);
            }
        } finally {
            pm.close();
        }
        return games;
    }
    
    @SuppressWarnings("unchecked")
    public Set<PlayerResult> getPlayerResults(Long gameId) {
        Set<PlayerResult> playerResults = new HashSet<PlayerResult>();
        
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            Query resultsQuery = pm.newQuery(PlayerResult.class);
            resultsQuery.setFilter("gameId == gameIdParam");
            resultsQuery.declareParameters("Long gameIdParam");
            
            List<PlayerResult> playerResultsList =
                (List<PlayerResult>) resultsQuery.execute(gameId);
            for (PlayerResult playerResult : playerResultsList) {
                playerResults.add(playerResult);
            }
        } finally {
            pm.close();
        }
        return playerResults;
    }
    
    public void postGame(Game game) {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(game);
        } finally {
            pm.close();
        }
    }
    
    public void postPlayerResult(PlayerResult playerResult) {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(playerResult);
        } finally {
            pm.close();
        }
        
        if (memCache != null) {
            memCache.clear();
        }
    }
    
    @SuppressWarnings("unchecked")
    public Set<PlayerStats> getStats(GetStatsParams params) {
        // Stats are cached?
        if ((memCache != null) && (memCache.containsKey(params))) {
            return (Set<PlayerStats>) memCache.get(params);
        }
        
        // Generate stats and add to cache
        Set<PlayerStats> stats = getStatsFromPersistence(params);
        if (memCache != null) {
            memCache.put(params, stats);
        }
        return stats;
    }
    
    private Set<PlayerStats> getStatsFromPersistence(GetStatsParams params) {
        // Retrieve game results
        Map<Game, Set<PlayerResult>> gameResults = getGameResults(
            params.groupId, new Date(params.fromDateMs), new Date(params.toDateMs));
        
        // Create player-to-stats map
        Map<Long, PlayerStats> playersStats = new HashMap<Long, PlayerStats>();
        for (Game game : gameResults.keySet()) {
            for (PlayerResult playerResult : gameResults.get(game)) {
                if (!playersStats.containsKey(playerResult.player)) {
                    playersStats.put(
                	    playerResult.player, new PlayerStats(playerResult.player));
                }
            }
        }
        
        // Update statistics
        basicStats.updateStats(gameResults, playersStats);
        pokerRankStats.updateStats(gameResults, playersStats);
        groupExpStats.updateStats(gameResults, playersStats);
        rorStats.updateStats(gameResults, playersStats);
        rivalsStats.updateStats(gameResults, playersStats);
        
        // Return
        Set<PlayerStats> playersStatsSet = new HashSet<PlayerStats>();
        for (PlayerStats stats : playersStats.values()) {
            playersStatsSet.add(stats);
        }
        return playersStatsSet;
    }
    
    @SuppressWarnings("unchecked")
    private Map<Game, Set<PlayerResult>> getGameResults(
	    Long groupId, Date fromDate, Date toDate) {
	
        Map<Game, Set<PlayerResult>> gameResults =
            new HashMap<Game, Set<PlayerResult>>();
        
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
    	    Query gamesQuery = pm.newQuery(Game.class);
    	    gamesQuery.setFilter(
    	            "groupId == groupIdParam" +
    	            " && date >= fromDateParam" +
    	            " && date < toDateParam");
    	    gamesQuery.declareImports("import java.util.Date");
    	    gamesQuery.declareParameters(
    		    "Long groupIdParam, Date fromDateParam, Date toDateParam");
    	    
    	    Query resultsQuery = pm.newQuery(PlayerResult.class);
    	    resultsQuery.setFilter("gameId == gameIdParam");
    	    resultsQuery.declareParameters("Long gameIdParam");
    	 
    	    List<Game> games = (List<Game>) gamesQuery.execute(
    		    groupId, fromDate, toDate);
    	    for (Game game : games) {
    	        Set<PlayerResult> resultsSet = new HashSet<PlayerResult>();
    	        
    	        List<PlayerResult> results =
    	            (List<PlayerResult>) resultsQuery.execute(game.id);
    	        
    	        for (PlayerResult result : results) {
    	            resultsSet.add(result);
    	        }
    	        
    	        gameResults.put(game, resultsSet);
    	    }
    	} finally {
    	    pm.close();
    	}
	
    	return gameResults;
    }

    private final BasicStatsCalculator basicStats = new BasicStatsCalculator();
    private final PokerRankCalculator pokerRankStats = new PokerRankCalculator();
    private final GroupExpectationCalculator groupExpStats = new GroupExpectationCalculator();
    private final RiskOfRuinCalculator rorStats = new RiskOfRuinCalculator(1000);
    private RivalsCalculator rivalsStats = new RivalsCalculator(10);
    
    private Cache memCache;
}
